package org.bdware.doaclient;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.apache.commons.cli.*;
import org.apache.log4j.Logger;
import org.bdware.doip.core.crypto.GlobalCertifications;
import org.bdware.doip.core.doipMessage.DoipMessage;
import org.bdware.doip.core.model.digitalObject.DigitalObject;
import org.bdware.doip.core.model.digitalObject.DoType;
import org.bdware.doip.core.model.metadata.SearchParameter;
import org.bdware.doip.core.utils.GlobalConfigurations;
import org.bdware.doip.endpoint.doipClient.DoipClient;
import org.bdware.doip.endpoint.doipClient.DoipClientImpl;
import org.bdware.doip.endpoint.doipClient.DoipMessageCallback;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.security.Security;

public class DOIPCMDClient {
    static Logger logger = Logger.getLogger(DOIPCMDClient.class);
    public static void main(String[] args) {
        final SyncCallback cb;
        ResponseContainer container = new ResponseContainer();
        cb = new SyncCallback(container);
        DoipMessage doipMessage;
        long TimeOut = 3000L;
        Options opts = getOptions();
        CommandLineParser parser = new DefaultParser();
        CommandLine cl;
        HelpFormatter hf = new HelpFormatter();
        hf.setWidth(120);
        String header = "basic doip operations to illustrate usage of the protocol\n\n";
        String footer = "\nPlease report issues at https://gitee.com/BDWare/doip-sdk.git";
        init();
        DoipClient doipClient = new DoipClientImpl();
        String confPath = "default_client.json";
        ClientConfig clientConfig = loadFromConfigFile(confPath);
        if (clientConfig == null) {
            logger.fatal("fail to load config file: " + confPath);
            System.exit(1);
        }

        GlobalConfigurations.userID = clientConfig.userID;
        GlobalConfigurations.CordraUsername = clientConfig.userName;
        GlobalConfigurations.CordraPassword = clientConfig.userPass;

        try {
            cl = parser.parse(opts, args);
            if (cl.getOptions().length == 0 || cl.hasOption('h')) {
                hf.printHelp("DOIPCMDClient", header, opts, footer, true);
            } else {
                doipClient.connect(clientConfig.repoURL);

                if (cl.hasOption('i')) {
                    doipClient.hello(clientConfig.repoID, cb);
                    getResp(cb, container, TimeOut);
                } else if (cl.hasOption('l')) {
                    doipClient.listOperations(clientConfig.repoID, cb);
                    getResp(cb, container, TimeOut);
                } else if (cl.hasOption('r')) {
                    String doiId = cl.getOptionValue("r");
                    if (doiId != null && !doiId.isEmpty()) {
                        doipClient.retrieve(doiId, null, true, cb);
                        getResp(cb, container, TimeOut);
                    }
                } else if (cl.hasOption('c')) {
                    String doId = cl.getOptionValues("c")[0];
                    String doType = cl.getOptionValues("c")[1];
                    String doCont = cl.getOptionValues("c")[2];

                    DoType doTypeFormal;
                    if (doType.equals("DO")) {
                        doTypeFormal = DoType.DO;
                    } else if (doType.equals("Meta")) {
                        doTypeFormal = DoType.Metadata;
                    } else {
                        doTypeFormal = DoType.UnKnown;
                    }

                    DigitalObject digitalObject = new DigitalObject(doId, doTypeFormal);
                    JsonObject jsonObject = new JsonObject();
                    jsonObject.addProperty("create", doCont);
                    jsonObject.addProperty("name", "create");
                    jsonObject.addProperty("description", "this is an example for create");
                    digitalObject.addAttribute("content", jsonObject);
                    doipClient.create(clientConfig.repoID, digitalObject, cb);
                    getResp(cb, container, TimeOut);
                } else if (cl.hasOption('u')) {
                    String doId = cl.getOptionValues("u")[0];
                    String doType = cl.getOptionValues("u")[1];
                    String doCont = cl.getOptionValues("u")[2];
                    DoType doTypeFormal;
                    if (doType.equals("DO")) {
                        doTypeFormal = DoType.DO;
                    } else if (doType.equals("Meta")) {
                        doTypeFormal = DoType.Metadata;
                    } else {
                        doTypeFormal = DoType.UnKnown;
                    }

                    DigitalObject digitalObject = new DigitalObject(doId, doTypeFormal);
                    JsonObject jsonObject = new JsonObject();
                    jsonObject.addProperty("update", doCont);
                    jsonObject.addProperty("name", "update");
                    jsonObject.addProperty("description", "this is an example for update");
                    digitalObject.addAttribute("content", jsonObject);
                    doipClient.update(digitalObject, cb);
                    getResp(cb, container, TimeOut);
                } else if (cl.hasOption('d')) {
                    String doiId = cl.getOptionValue("d");
                    if (doiId != null && !doiId.isEmpty()) {
                        doipClient.delete(doiId, cb);
                        getResp(cb, container, TimeOut);
                    }
                } else if (cl.hasOption('s')) {
                    String keyword = cl.getOptionValue("s");
                    SearchParameter searchParameter = new SearchParameter(keyword, 0, 10, "id:ASC", "full");
                    doipClient.search(clientConfig.repoID, searchParameter, cb);
                    getResp(cb, container, TimeOut);
                }
            }
            if (doipClient.isConnected()) {
                logger.debug("close channel");
                doipClient.close();
                System.exit(0);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    public static Options getOptions() {
        Options opts = new Options();
        opts.addOption("h", false, "display this help message");
        opts.addOption(Option.builder("i").longOpt("hello").hasArg(false).desc("get doip service information, e.g. -i").build());
        opts.addOption(Option.builder("c").longOpt("create").hasArg(true).desc("create do in doip repository/registry, e.g. create do: -c 86.5000.470/do.test DO message, create meta: -c 86.5000.470/do.test Meta description").numberOfArgs(3).build());
        opts.addOption(Option.builder("l").longOpt("list").hasArg(false).desc("get repository/registry supported operations, e.g. -l").build());
        opts.addOption(Option.builder("r").longOpt("retrieve").hasArg(true).desc("retrieve do in doip repository/registry, e.g. -r 86.5000.470/do.test").numberOfArgs(1).build());
        opts.addOption(Option.builder("s").longOpt("search").hasArg(true).desc("search meta info in registry, e.g. -s key_word").numberOfArgs(1).build());
        opts.addOption(Option.builder("u").longOpt("update").hasArg(true).desc("update do in doip repository/registry, e.g. -u 86.5000.470/do.test DO new_message").numberOfArgs(3).build());
        opts.addOption(Option.builder("d").longOpt("delete").hasArg(true).desc("delete do in doip repository/registry, e.g. -d 86.5000.470/do.test").numberOfArgs(1).build());
        return opts;
    }

    static class SyncCallback implements DoipMessageCallback {
        ResponseContainer responseContainer;

        public SyncCallback(ResponseContainer container){
            responseContainer = container;
        }

        @Override
        public synchronized void onResult(DoipMessage msg) {

            responseContainer.response = msg;
            this.notifyAll();
        }
    }

    static class ResponseContainer{
        DoipMessage response;
    }

    public static void init(){
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }

        try {
            GlobalCertifications.loadKeysFromJKS(GlobalConfigurations.JKSPath, GlobalConfigurations.JKSPassword);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static DoipMessage getResp(SyncCallback cb, ResponseContainer container, long TimeOut) {
        synchronized (cb) {
            try {
                cb.wait(TimeOut);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return container.response;
    }

    public static ClientConfig loadFromConfigFile(String filePath){
        logger.info("load config from: " + filePath);
        try{
            String configJson = readFile(filePath);
            return new Gson().fromJson(configJson, ClientConfig.class);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private static String readFile(String path) throws Exception{
        File file = new File(path);
        logger.debug("conf file absolute path: "+file.getAbsolutePath());
        StringBuilder configJson = new StringBuilder();
        BufferedReader br = new BufferedReader(new FileReader(file));
        String line = br.readLine();
        while(line != null){
            configJson.append(line);
            line = br.readLine();
        }
        br.close();
        return configJson.toString();
    }

    class ClientConfig {
        public String repoID;
        public String repoURL;
        public String userID;
        public String userName;
        public String userPass;
    }
}
